package android.theHead;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.TelephonyManager;
import android.util.Log;

// TODO: Auto-generated Javadoc
/**
 * The Class BackgroundService.
 */
public class BackgroundService extends Service{
	// action name for broadcasting the data received from the webservice
	/** The Constant NEW_HEAD_DATA. */
	public static final String NEW_HEAD_DATA = "New_Head_Data";
	public static final String NEW_HEAD_READINGS = "New_readings";
	
	public static final String HEAD_DATA_CHANGED = "head_changed";
	public static final String HEAD_READINGS_FIELD = "head_readings";
	
	public static final int READINGS_TABLE_UPDATED = 1;
	
	// URL of the webservice ised for data exchange
	private static String URL_DATA_WEBSERVICE = "http://www.head.pmast.de/makeEntry.php";
	
	// a timer object to handle the continuous database update
		/** The INTERVAL. */
	private static int INTERVAL = 1000;
	
	
	int simulationCounter = 0;

	// create a new database adapter for the communication with the database 
	/** The head db. */
	HeadDBAdapter headDb = new HeadDBAdapter(this);

	/** The this context. */
	Service thisContext = this;

	// Acquire a reference to the system Location Manager
	/** The location manager. */
	LocationManager locationManager = null;

	// Define a listener that responds to location updates
	MyLocationListener locationListener = new MyLocationListener(); 
	
	TempoListener tempoListener;// = new TempoListener(this);
	DataExchange dataExchange;
	
	// initialize variables used for scheduled code execution
	private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
	private ScheduledFuture<?> dataExchangeHandle;

	/* (non-Javadoc)
	 * @see android.app.Service#onBind(android.content.Intent)
	 */
	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}

	/* (non-Javadoc)
	 * @see android.app.Service#onCreate()
	 */
	public void onCreate(){
	}

	/* (non-Javadoc)
	 * @see android.app.Service#onDestroy()
	 */
	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.i("BackgroundService","destroying!");
		stopService();
	}

	/* (non-Javadoc)
	 * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i("BackgroundService", "started");
		startService();
		return Service.START_STICKY;
	}

	/**
	 * Method that centralizes the activities that has to be performed when the service starts
	 * Those are:
	 * - open a connection to the database
	 * - start scheduled activity for data exchange
	 * - initialize and start the location listener to receive GPS data.
	 */
	public void startService(){
		// open the connection to the database
		headDb.open();

		Log.i("BackgroundService", "start service");
		tempoListener = new TempoListener(this);
		dataExchange = new DataExchange(headDb, thisContext);
		
		// start the scheduled process for data exchange with the server
		dataExchangeHandle = scheduler.scheduleAtFixedRate(dataExchange, 100, INTERVAL, TimeUnit.MILLISECONDS);

		// startup the location listener to receive updates from the device's GPS device
		locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);

	}

	/**
	 * Method that centralizes stopping the activities that were started in {@link startService}.
	 */
	public void stopService(){
		Log.i("BackgroundService", "stop service");
		// stop the data updates
		tempoListener.stopListener();
		if (dataExchangeHandle != null){
			dataExchangeHandle.cancel(false);
			dataExchangeHandle = null;
			scheduler.shutdown();
			try {
				scheduler.awaitTermination(5, TimeUnit.SECONDS);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			scheduler = null;
		}

		// stop the location listener updates
		if (locationManager!=null){
			locationManager.removeUpdates(locationListener);
		}

		// close the connection to the database
		headDb.close();
	}

	
	private HeadReadings simulate(int simulationCounter){
		double[] item = Simulator.simData[simulationCounter];
		HeadReadings entry = new HeadReadings();
		entry.latitude = item[0];
		entry.longitude = item[1];
		entry.speed = (float)item[2];
		entry.tempo = (float)item[3];
		return entry;
	}
	
	private void saveReadingsInDB(){
		Log.i("BackgroundService","savereadings	InDB");
		Location oldLocation = locationListener.lastLocation;
		HeadReadings entry;
		if (oldLocation != null){
			entry = new HeadReadings(oldLocation);
			
			// if there is already a DB entry get the distance to the last entry
			HeadReadings oldEntry = headDb.getLastEntry();
			
			if (oldEntry!=null){
				Location lastLocation = oldEntry.getLocation();
				entry.distance = oldLocation.distanceTo(lastLocation);
			} else {
				entry.distance = 0;
			}
		} else {
			entry = new HeadReadings();
		}
		entry.tempo = tempoListener.getTempo();
		//entry.tempo_avg = tempoListener.getTempoAvg();

//<<<<< simulation start >>>>>>>>///
//		if (simulationCounter==Simulator.simData.length){
//			simulationCounter = 0;
//		}
//		entry = simulate(simulationCounter++);
//<<<<< simulation start >>>>>>>>///

		
		// insert the new entry to the database
		headDb.insertEntry(entry);
		
		broadcastReadings(entry);
	}

	private void broadcastReadings(HeadReadings entry){
		Log.i("BackgroundService", "broadcasting readings");

		Intent locationIntent = new Intent(NEW_HEAD_READINGS);
		locationIntent.putExtra(HEAD_READINGS_FIELD, entry);
		sendBroadcast(locationIntent);
		
		sendBroadcast(locationIntent);
	}

	/**
	 * The listener interface for receiving myLocation events.
	 * The class that is interested in processing a myLocation
	 * event implements this interface, and the object created
	 * with that class is registered with a component using the
	 * component's <code>addMyLocationListener<code> method. When
	 * the myLocation event occurs, that object's appropriate
	 * method is invoked.
	 *
	 * @see MyLocationEvent
	 */
	public class MyLocationListener implements LocationListener{
		Location lastLocation;
		
		/* (non-Javadoc)
		 * @see android.location.LocationListener#onLocationChanged(android.location.Location)
		 */
		public void onLocationChanged(Location location) {
			this.lastLocation = location;
		}

		/* (non-Javadoc)
		 * @see android.location.LocationListener#onStatusChanged(java.lang.String, int, android.os.Bundle)
		 */
		public void onStatusChanged(String provider, int status, Bundle extras) {}

		/* (non-Javadoc)
		 * @see android.location.LocationListener#onProviderEnabled(java.lang.String)
		 */
		public void onProviderEnabled(String provider) {}

		/* (non-Javadoc)
		 * @see android.location.LocationListener#onProviderDisabled(java.lang.String)
		 */
		public void onProviderDisabled(String provider) {}
	}

	public class DataExchange implements Runnable {		
		private HeadDBAdapter headDB;
		private Context context;
		
		public DataExchange(HeadDBAdapter db, Context context){
			this.headDB = db;
			this.context = context;
		}

		public void run() {
			  Date now = new Date();
			  String nowTime = now.toString();
			  Log.i("UploadData", "Start at " + nowTime);
			  // TODO: urgent!
			  // save readings in DB has to happen every time there are new readings
			  // should not matter whether those readings are GPS data or Tempo measurements
			  saveReadingsInDB();
			  submitDB();
			  
			  now = new Date();
			  nowTime = now.toString();
			  Log.i("UploadData", "Stop at " + nowTime);
		}
		
		public void submitDB(){

			JSONArray unsentEntries = headDB.getUnsentEntries();
			int lastKey = headDB.getLastKey();
			
			Log.i("submitDB","lastkey: " + lastKey);
			Log.i("submitDB","# unsent Entries: " + unsentEntries.length());
			
			TelephonyManager telephonyManager = (TelephonyManager)this.context.getSystemService(Context.TELEPHONY_SERVICE);

		    Date now = new Date();
		    long deviceTime = now.getTime();
			
			List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
	        nameValuePairs.add(new BasicNameValuePair("data", unsentEntries.toString()));
	        nameValuePairs.add(new BasicNameValuePair("deviceID", telephonyManager.getDeviceId()));
	        nameValuePairs.add(new BasicNameValuePair("deviceTime", Long.toString(deviceTime)));
	        
	        PostData poster = new PostData();
	        
	        String result = poster.connect(URL_DATA_WEBSERVICE, nameValuePairs);
	        Log.i("submitDB", "Result: " + result);
	        
	        int transferSucces= 0;
	        
	        JSONObject jsonResults = null;
	        try {
	        	jsonResults = new JSONObject(result);
	            if (jsonResults.has("uploadStatus") && jsonResults.getInt("uploadStatus")==1){
	            	Log.i("submitDB","succes");
	            	transferSucces = 1;
	            } else {
	            	Log.i("submitDB","error message received");
	            }
	        } catch (JSONException e) {
	    		e.printStackTrace();
	    	}
	        
	        if (transferSucces ==1){
	        	headDB.markUnsetSent(lastKey);
	        }
	        storeData(jsonResults);
	        broadcastDataChange();
		}
		
		private void storeData(JSONObject jsonResult){
			Log.i("BackgroundService", "start storing received data");
			
			TeamDBAdapter teamDB = new TeamDBAdapter(thisContext);
			teamDB.open();
			teamDB.insertNewReadings(jsonResult);
			teamDB.cleanReadings();
			teamDB.close();
			Log.i("BackgroundService", "stop storing received data");
		}
		
		private void broadcastDataChange(){
			Log.i("BackgroundService", "broadcasting change");
			// send out the new data for internal use
			Intent locationIntent = new Intent(NEW_HEAD_DATA);
			locationIntent.putExtra(HEAD_DATA_CHANGED, READINGS_TABLE_UPDATED);
			sendBroadcast(locationIntent);
		}
		
	}
}
